﻿using System;
using System.IO;
using System.Linq;
using LukasKopenec.LocalizationSupport;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// Implements an undoable command for creating a new resource.
    /// </summary>
    /// <remarks>
    /// Input Parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name (Type)
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     FileName (<see cref="String"/>)
    /// </term>
    /// <description>
    ///     The name and path to the file that will be added to resources.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     Parent (<see cref="Parent"/>)
    /// </term>
    /// <description>
    ///     The directory in which the new resource will be created.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     Project (<see cref="Elements.Project"/>)
    /// </term>
    /// <description>
    ///     The Project in which the new resource will be created.
    /// </description>
    /// </item>
    /// </list>
    /// Output Parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name (Type)
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     CreatedResource (<see cref="Elements.Resource"/>)
    /// </term>
    /// <description>
    ///     The created resource.
    /// </description>
    /// </item>
    /// </list>
    /// The command can execute if all of its input parameters are set.
    /// </remarks>
    public class NewResourceCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="NewResourceCommand"/> instance.
        /// </summary>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack on which the command will be put after its execution.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack on which the command will be put when it has been undone.
        /// </para>
        /// </param>
        public NewResourceCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<string>("FileName", ParameterDirection.In);
            AddParameter<Directory>("Parent", ParameterDirection.In);
            AddParameter<Elements.Project>("Project", ParameterDirection.In);
            AddParameter<Elements.Resource>("CreatedResource", ParameterDirection.Out);

            Name = (string)LocalizationManager.GetValue("Commands.Names.NewResource");
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Copies a content of a source stream to a destination stream.
        /// </summary>
        /// <param name="source">Source</param>
        /// <param name="destination">Destination</param>
        private void CopyStream(Stream source, Stream destination)
        {
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            while (true)
            {
                int read = source.Read(buffer, 0, buffer.Length);
                if (read <= 0)
                {
                    return;
                }
                destination.Write(buffer, 0, read);
            }
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var resourceName = Path.GetFileName(FileName);
            int counter = 1;
            
            while (Parent.Children.Any(child => child.DisplayName == resourceName))
                resourceName = Path.GetFileNameWithoutExtension(FileName) + counter++ + Path.GetExtension(FileName);

            var virtualPath = Parent.VirtualPath + "/" + resourceName;
            var resourcePart = Project.AdaptedPart.Package.ProjectPart.CreateResourcePart(virtualPath, FileName);
            CreatedResource = Internal.ProjectExplorerItemHelper.FindItem(virtualPath, Project.GetRootDirectory(Elements.RootDirectory.Resources)) as Elements.Resource;
            CreatedResource.IsSelected = true;
            
            Project.AutoSave();
            Project.IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            Project.AdaptedPart.Package.RestorePart(CreatedResource.AdaptedPart);

            var source = stackPart.GetStream();
            var dest = CreatedResource.AdaptedPart.GetStream(FileMode.Create);
            CopyStream(source, dest);
            source.Close();
            dest.Close();
            ((Model.Elements.ProjectElement)Project.AdaptedElement).Resources.Add(Parent.VirtualPath + "/" + CreatedResource.Name, CreatedResource.AdaptedPart.Uri);
            Project.StackFile.DeletePart(stackPart.Uri);
            stackPart = null;

            Project.AutoSave();
            Project.IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            var rnd = new Random();

            var uri = new Uri("/" + rnd.Next().ToString(), UriKind.Relative);
            while (Project.StackFile.PartExists(uri))
                uri = new Uri("/" + rnd.Next().ToString(), UriKind.Relative);

            stackPart = Project.StackFile.CreatePart(uri, CreatedResource.AdaptedPart.ContentType, System.IO.Packaging.CompressionOption.Maximum);
            var source = CreatedResource.InputStream;
            var dest = stackPart.GetStream(FileMode.Create);
            CopyStream(source, dest);
            source.Close();
            dest.Close();
            Project.StackFile.Flush();
            Project.AdaptedPart.Package.DeletePart(CreatedResource.AdaptedPart.Uri);
            ((Model.Elements.ProjectElement)Project.AdaptedElement).Resources.Remove(CreatedResource.AdaptedPart.Uri);

            Project.AutoSave();
            Project.IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the created resource.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.Resource"/>
        /// </para>
        /// <para>
        /// The created resource.
        /// This property wraps the "CreatedResource" parameter.
        /// </para>
        /// </value>
        public Elements.Resource CreatedResource
        {
            get { return GetParameterValue<Elements.Resource>("CreatedResource"); }
            private set { SetParameterValue<Elements.Resource>("CreatedResource", value); }
        }

        /// <summary>
        /// Gets or sets the name and path of the file that will be loaded to resources.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The name and path of the file that will be loaded to the project resources.
        /// This property wraps the "FileName" parameter.
        /// </para>
        /// </value>
        public string FileName
        {
            get { return GetParameterValue<string>("FileName"); }
            set { SetParameterValue<string>("FileName", value); }
        }

        /// <summary>
        /// Gets or sets the directory in which the resource will be inserted.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Directory"/>
        /// </para>
        /// <para>
        /// The directory in which the resource will be inserted.
        /// </para>
        /// </value>
        public Directory Parent
        {
            get { return GetParameterValue<Directory>("Parent"); }
            set { SetParameterValue<Directory>("Parent", value); }
        }

        /// <summary>
        /// Gets or sets the project to which the resource will be added.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.Project"/>
        /// </para>
        /// <para>
        /// The project to which the resource will be added.
        /// This property wraps the "Project" command parameter.
        /// </para>
        /// </value>
        public Elements.Project Project
        {
            get { return GetParameterValue<Elements.Project>("Project"); }
            set { SetParameterValue<Elements.Project>("Project", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the part in the project's stack file that contains the copy of the deleted resource.
        /// </summary>
        private System.IO.Packaging.PackagePart stackPart;

        #endregion
    }
}
